Skip to content

S01-05 基础-Scanner

[TOC]

概述

Scanner:是 Java 1.5 引入的便捷输入处理工具类,位于java.util包下,核心作用是从控制台、文件、字符串、输入流等数据源中解析出不同类型的数据(基本类型、字符串等),底层封装了BufferedReader做缓冲处理,屏蔽了底层输入流的复杂操作,是Java入门和日常开发中最常用的输入方式,无需手动处理缓冲区、编码解析等底层细节。

核心特性

Scanner 核心特性:

  1. 数据源灵活
    支持System.in(控制台)、File(文件)、String(字符串)、InputStream(字节流)、Readable(字符流)等多种输入源。

  2. 类型解析丰富
    内置方法直接解析int/long/float/double/boolean等所有基本类型,以及字符串,无需手动类型转换。

  3. 分隔符可自定义
    默认以空白字符(空格、回车\n、制表符\t、换页符\f)为分隔符,也可通过正则表达式自定义分隔符(如逗号、分号)。

  4. 输入验证机制
    提供hasNextXxx()系列方法,提前判断下一个输入是否为目标类型,避免输入不匹配导致的InputMismatchException异常。

  5. 自动关闭支持
    实现AutoCloseable接口,可通过try-with-resources语法自动关闭资源,避免输入流泄漏。

  6. 存在局限性
    线程不安全(@NotThreadSafe),大文件读取效率低于BufferedReader,不支持直接读取char类型(需间接处理)。

基本使用

基本使用步骤

使用Scanner的核心流程为导入包 → 创建对象 → 读取数据 → 关闭资源,四步缺一不可(导入包和关闭资源易被初学者忽略)。

完整基础示例(控制台输入):

java
// 1. 导入Scanner类,必须显式导入,否则编译报错
import java.util.Scanner;

public class ScannerBasic {
    public static void main(String[] args) {
        // 2. 创建Scanner对象,指定数据源为控制台标准输入System.in
        Scanner scanner = new Scanner(System.in);

        // 3. 读取数据:提示用户输入并解析
        System.out.print("请输入一个整数:");
        int num = scanner.nextInt(); // 读取整数
        System.out.print("请输入一个字符串:");
        String str = scanner.next(); // 读取无空格字符串

        // 输出读取结果
        System.out.println("你输入的整数:" + num);
        System.out.println("你输入的字符串:" + str);

        // 4. 关闭Scanner:释放底层输入流资源,避免内存泄漏
        scanner.close();
    }
}

资源关闭的最佳实践

资源关闭的最佳实践:

手动调用close()可能因异常导致资源未关闭,Java 7+ 推荐使用try-with-resources语法,括号内创建的Scanner会在代码块执行完毕后自动调用close(),无需手动编写,且异常时也能保证关闭。

java
import java.util.Scanner;

public class ScannerAutoClose {
    public static void main(String[] args) {
        // try-with-resources:括号内实现AutoCloseable的对象自动关闭
        try (Scanner scanner = new Scanner(System.in)) {
            System.out.print("请输入你的姓名:");
            String name = scanner.nextLine();
            System.out.println("欢迎你:" + name);
        } // 此处自动执行scanner.close(),无需手动写
    }
}

构造方法

Scanner 构造方法(指定数据源):

Scanner提供多个构造方法适配不同输入场景,核心常用构造方法如下(按使用频率排序),部分文件/流相关构造方法会抛出受检异常,需处理try-catchthrows

构造方法数据源说明异常处理适用场景
Scanner(InputStream source)字节输入流,最常用System.in控制台交互式输入
Scanner(String source)字符串数据源测试输入、解析固定格式字符串
Scanner(File file)本地文本文件抛出FileNotFoundException读取文本文件内容
Scanner(File file, String charset)本地文件+指定编码抛出FileNotFoundException避免文件中文乱码
Scanner(Readable source)字符流(如BufferedReader自定义字符输入流

构造方法使用示例:

java
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ScannerSource {
    public static void main(String[] args) throws FileNotFoundException {
        // 1. 控制台输入(最常用)
        Scanner sc1 = new Scanner(System.in);

        // 2. 字符串数据源:按默认分隔符解析
        Scanner sc2 = new Scanner("10 20.5 Java true");
        int a = sc2.nextInt();       // 解析整数10
        double b = sc2.nextDouble(); // 解析浮点数20.5
        String s = sc2.next();       // 解析字符串Java
        boolean flag = sc2.nextBoolean(); // 解析布尔值true
        System.out.println(a + "," + b + "," + s + "," + flag); // 10,20.5,Java,true

        // 3. 文件数据源:指定UTF-8编码避免中文乱码
        File file = new File("test.txt"); // 项目根目录下的文本文件
        Scanner sc3 = new Scanner(file, "UTF-8");
        while (sc3.hasNextLine()) {
            String line = sc3.nextLine(); // 逐行读取文件
            System.out.println("文件内容:" + line);
        }

        // 关闭资源
        sc1.close();
        sc2.close();
        sc3.close();
    }
}

核心方法

Scanner 核心方法(按功能分类):

Scanner的方法分为输入验证方法hasNextXxx())和数据读取方法nextXxx()),两者一一对应,推荐先验证再读取,避免输入类型不匹配的异常。所有方法都会自动跳过数据源中的空白字符(除非自定义分隔符)。

基本类型

基本类型:验证+读取方法:

适用于解析int/long/float等8种基本类型,布尔类型仅识别true/false(忽略大小写),其他类型需输入对应格式的数值(如浮点数需包含.或科学计数法)。

验证方法读取方法功能说明输入示例
hasNextInt()nextInt()验证/读取int类型10、-5
hasNextLong()nextLong()验证/读取long类型100L、12345678901
hasNextFloat()nextFloat()验证/读取float类型3.14f、5.0
hasNextDouble()nextDouble()验证/读取double类型6.28、1.2e3
hasNextBoolean()nextBoolean()验证/读取boolean类型true、FALSE
hasNextByte()nextByte()验证/读取byte类型(-128~127)100、-50
hasNextShort()nextShort()验证/读取short类型(-32768~32767)2000、-1000

带验证的基本类型读取示例(避坑核心):

若直接调用nextInt()但用户输入非整数,会抛出InputMismatchException必须用hasNextXxx()先判断,再读取,且无效输入需用next()丢弃,否则会陷入死循环。

java
import java.util.Scanner;

public class ScannerBasicType {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner(System.in)) {
            // 读取整数:循环验证,直到输入合法
            System.out.print("请输入一个整数:");
            while (!sc.hasNextInt()) {
                System.out.print("输入错误!请重新输入整数:");
                sc.next(); // 丢弃无效输入,关键步骤(否则死循环)
            }
            int num = sc.nextInt();

            // 读取浮点数:同理验证
            System.out.print("请输入一个浮点数:");
            while (!sc.hasNextDouble()) {
                System.out.print("输入错误!请重新输入浮点数:");
                sc.next();
            }
            double d = sc.nextDouble();

            System.out.println("合法整数:" + num + ",合法浮点数:" + d);
        }
    }
}

字符串读取

字符串读取:next() vs nextLine()(重点区分,避坑核心):

Scanner提供两种字符串读取方法,差异极大,是初学者最易踩坑的点,核心区别在于是否识别空白字符整行数据

方法名核心规则终止符是否包含空白字符适用场景
next()1. 跳过开头所有空白字符;2. 读取到第一个空白字符时停止;3. 仅返回有效字符空格/回车/制表符读取单个单词、无空格字符串
nextLine()1. 从当前位置读取到换行符\n;2. 包含中间所有空白字符;3. 读取后丢弃换行符换行符\n读取完整行、含空格的字符串

基础使用示例

基础使用示例:

java
import java.util.Scanner;

public class ScannerString {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner(System.in)) {
            System.out.print("请输入单个单词(无空格):");
            String word = sc.next(); // 输入Hello World → 仅读取Hello
            System.out.println("next()读取结果:" + word);

            sc.nextLine(); // 丢弃上一步残留的换行符(后续讲解坑点)

            System.out.print("请输入整行内容(可含空格):");
            String line = sc.nextLine(); // 输入Hello World → 读取完整内容
            System.out.println("nextLine()读取结果:" + line);
        }
    }
}

最经典坑点

最经典坑点:nextXxx() 与 nextLine() 混用导致空行:

问题原因nextInt()/nextDouble()/next()等方法读取完有效数据后,输入缓冲区中会残留一个换行符\n,后续调用nextLine()会直接读取这个换行符,返回空字符串。

java
import java.util.Scanner;

// 坑点演示:nextInt()后调用nextLine()返回空
public class ScannerPitfall {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner(System.in)) {
            System.out.print("请输入年龄:");
            int age = sc.nextInt(); // 读取年龄后,缓冲区残留\n
            System.out.print("请输入姓名:");
            String name = sc.nextLine(); // 直接读取\n,返回空字符串
            System.out.println("年龄:" + age + ",姓名:'" + name + "'"); // 姓名为空
        }
    }
}

坑点解决方案(两种,推荐方案2):

方案1:读取完基本类型后,手动调用sc.nextLine()丢弃残留换行符

java
import java.util.Scanner;

public class ScannerFix1 {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner(System.in)) {
            System.out.print("请输入年龄:");
            int age = sc.nextInt();
            sc.nextLine(); // 关键:丢弃缓冲区的\n
            System.out.print("请输入姓名:");
            String name = sc.nextLine(); // 正常读取
            System.out.println("年龄:" + age + ",姓名:" + name);
        }
    }
}

方案2:统一使用nextLine()读取所有输入,再手动解析为目标类型(推荐)

彻底避免分隔符/换行符问题,所有输入先读为字符串,再通过Integer.parseInt()/Double.parseDouble()等方法转换为对应类型,是实际开发中最稳妥的方式。

java
import java.util.Scanner;

public class ScannerFix2 {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner(System.in)) {
            // 统一用nextLine()读取,再手动解析
            System.out.print("请输入年龄:");
            String ageStr = sc.nextLine();
            int age = Integer.parseInt(ageStr); // 解析为int

            System.out.print("请输入体重(kg):");
            String weightStr = sc.nextLine();
            double weight = Double.parseDouble(weightStr); // 解析为double

            System.out.print("请输入家庭地址(可含空格):");
            String address = sc.nextLine(); // 直接读取字符串

            System.out.printf("年龄:%d,体重:%.1f,地址:%s\n", age, weight, address);
        } catch (NumberFormatException e) {
            System.err.println("输入类型错误,请输入合法的数值!");
        }
    }
}

读取字符

读取字符(char):无直接方法,间接实现:

Scanner没有提供nextChar()方法,若需读取单个字符,需通过next()nextLine()读取字符串后,调用charAt(0)获取字符串的第一个字符。

java
import java.util.Scanner;

public class ScannerChar {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner(System.in)) {
            System.out.print("请输入一个单个字符:");
            char ch = sc.next().charAt(0); // 读取字符串→取索引0的字符
            System.out.println("你输入的字符:" + ch);
            System.out.println("该字符的ASCII码:" + (int) ch);
        }
    }
}

逐行读取

逐行读取:hasNextLine() + nextLine():

适用于读取文件、控制台多行输入场景,hasNextLine()判断是否还有下一行数据,nextLine()读取整行,是处理文本的常用组合。

java
import java.util.Scanner;

// 控制台多行输入,输入exit退出
public class ScannerReadLine {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner(System.in)) {
            System.out.println("请输入内容(输入exit退出):");
            while (sc.hasNextLine()) { // 判断是否有下一行
                String line = sc.nextLine(); // 读取整行
                if ("exit".equals(line)) {
                    System.out.println("程序退出!");
                    break;
                }
                System.out.println("你输入的内容:" + line);
            }
        }
    }
}

通用判断

通用判断:hasNext() + next():

  • hasNext():判断数据源中是否还有下一个有效标记(按分隔符分割的内容),返回布尔值;
  • next():读取下一个有效标记,返回字符串; 两者是Scanner的基础方法,所有hasNextXxx()/nextXxx()都是基于此扩展。
java
import java.util.Scanner;

public class ScannerHasNext {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner("Java Python C++ Go")) {
            while (sc.hasNext()) { // 判断是否有下一个标记
                String lang = sc.next(); // 读取下一个标记
                System.out.println("编程语言:" + lang);
            }
        }
    }
}

进阶:自定义分隔符

Scanner 进阶用法:自定义分隔符:

Scanner默认以空白字符为分隔符,可通过useDelimiter(String regex)方法自定义分隔符(支持正则表达式),适用于解析固定格式的数据源(如CSV文件、逗号/分号分隔的日志、自定义协议数据)。

单个分隔符

基本自定义:单个分隔符:

如以逗号分号、**竖线|**为分隔符,直接传入字符即可。

java
import java.util.Scanner;

public class ScannerDelimiter1 {
    public static void main(String[] args) {
        // 数据源:逗号分隔的字符串
        Scanner sc = new Scanner("10,20.5,张三,true");
        sc.useDelimiter(","); // 自定义分隔符为逗号

        // 按逗号解析不同类型数据
        int num = sc.nextInt();
        double d = sc.nextDouble();
        String name = sc.next();
        boolean flag = sc.nextBoolean();

        System.out.printf("num:%d, d:%.1f, name:%s, flag:%b\n", num, d, name, flag);
        // 输出:num:10, d:20.5, name:张三, flag:true
        sc.close();
    }
}

多个分隔符

高级自定义:多个分隔符(正则表达式):

若数据源中有多种分隔符(如同时有逗号和分号),可传入正则表达式[分隔符1|分隔符2],匹配任意一个分隔符。

java
import java.util.Scanner;

public class ScannerDelimiter2 {
    public static void main(String[] args) {
        // 数据源:逗号+分号混合分隔
        Scanner sc = new Scanner("30;40.6,李四,false|50");
        sc.useDelimiter("[,;|]"); // 正则:匹配逗号、分号、竖线中的任意一个

        while (sc.hasNext()) {
            System.out.println(sc.next());
        }
        /* 输出: 30 40.6 李四 false 50 */
        sc.close();
    }
}

还原默认分隔符

还原默认分隔符:

若自定义分隔符后需恢复默认的空白字符分隔,调用useDelimiter("\\s+")即可(\\s+是正则,匹配一个或多个空白字符)。

java
sc.useDelimiter("\\s+"); // 还原默认空白字符分隔符

操作文件

Scanner 操作文件(完整示例):

Scanner可轻松读取文本文件内容,核心是指定文件路径+编码,避免中文乱码,结合hasNextLine()+nextLine()逐行读取,是小文件读取的便捷方式(大文件推荐BufferedReader)。

完整文件读取示例(处理异常+指定UTF-8):

java
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ScannerReadFile {
    public static void main(String[] args) {
        // 文件路径:相对路径(项目根目录)或绝对路径(如C:/test.txt)
        File file = new File("test.txt");
        // try-with-resources 自动关闭Scanner
        try (Scanner sc = new Scanner(file, "UTF-8")) {
            int lineNum = 1;
            System.out.println("文件内容(行号+内容):");
            while (sc.hasNextLine()) {
                String line = sc.nextLine();
                System.out.println(lineNum + ":" + line);
                lineNum++;
            }
        } catch (FileNotFoundException e) {
            System.err.println("文件读取失败:" + e.getMessage());
            System.err.println("请检查文件路径是否正确,或文件是否存在!");
        }
    }
}

常见问题与避坑指南

Scanner 常见问题与避坑指南:

  1. 输入类型不匹配:InputMismatchException

    • 原因:直接调用nextInt()/nextDouble()等方法,用户输入非对应类型数据(如输入"abc"却读int)。
    • 解决方案:先调用hasNextXxx()验证,再读取,无效输入用next()丢弃。
  2. nextLine() 读取空字符串(混用坑点)

    • 原因nextXxx()读取后缓冲区残留换行符\n
    • 解决方案:手动调用sc.nextLine()丢弃换行符,或统一用nextLine()读取后手动解析(推荐)。
  3. 中文乱码(控制台/文件)

    • 控制台乱码:配置IDE控制台编码为UTF-8(IDEA:Run/Debug Configurations → VM options添加-Dfile.encoding=UTF-8)。
    • 文件乱码:创建Scanner时显式指定编码为UTF-8,如new Scanner(file, "UTF-8")
  4. 读取大文件效率低

    • 原因:Scanner底层虽有缓冲,但包含大量类型解析逻辑,比纯缓冲的BufferedReader耗时。
    • 解决方案:读取GB级大文件时,改用BufferedReader或Java 8+的Files.lines()
  5. 线程不安全

    • 原因:Scanner的方法未做同步处理,多线程共享一个Scanner对象会导致数据读取异常。
    • 解决方案:多线程环境下,每个线程创建独立的Scanner对象,或使用ThreadLocal<Scanner>隔离。
  6. 关闭Scanner后,System.in被关闭

    • 原因:Scanner关联System.in时,调用sc.close()会同时关闭底层的System.in输入流,后续无法再读取控制台输入。
    • 解决方案:若程序中多次使用控制台输入,仅在程序最后关闭Scanner,或复用同一个Scanner对象。
  7. 无限循环(未丢弃无效输入)

    • 原因hasNextXxx()判断为false时,未调用sc.next()丢弃无效输入,导致hasNextXxx()一直返回false,循环无法退出。
    • 解决方案:在while (!sc.hasNextXxx())中,调用sc.next()丢弃无效输入。

对比其他输入方式

Scanner 与其他输入方式的对比:

Java中常用的控制台/文件输入方式有多种,Scanner与其他方式的核心对比如下,可根据场景选择:

输入方式优点缺点适用场景
ScannerAPI简洁、支持多类型解析、易用性强大文件效率低、线程不安全入门学习、日常开发、小规模输入/小文件读取
System.in.read()底层、无依赖、无需导入包仅读单个字节、需处理编码、中文乱码底层字节输入、简单字符读取
BufferedReader效率高、缓冲读取、支持逐行仅读字符串、需手动解析类型大文件读取、高性能场景
Console 类支持密码隐藏(readPassword())仅控制台环境、IDE中可能返回null控制台敏感信息输入(密码/验证码)

核心使用总结

Scanner 核心使用总结:

  1. 核心流程:导入java.util.Scanner → 创建对象指定数据源 → 先hasNextXxx()验证再nextXxx()读取 → 关闭资源(优先try-with-resources)。
  2. 字符串读取:优先区分next()(无空格)和nextLine()(含空格),避免与基本类型方法混用,推荐统一用nextLine()后手动解析。
  3. 自定义分隔符:解析固定格式数据时,用useDelimiter()指定分隔符,支持正则表达式匹配多分隔符。
  4. 文件读取:小文件用Scanner+UTF-8编码,大文件改用BufferedReader
  5. 异常处理:必须处理InputMismatchException(类型不匹配)、FileNotFoundException(文件不存在)、NumberFormatException(手动解析失败)。
  6. 避坑核心:验证后读取、丢弃无效输入、处理换行符、指定UTF-8编码、避免多线程共享。

掌握Scanner的以上用法,能轻松解决Java入门和日常开发中90%以上的输入处理需求,是Java基础中必备的工具类技能。